# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Name:        client_main.py
# Purpose:     GAEDrive Client: file operations for fuse.py
#
# Author:      Crane Chu <cranechu (at) gmail.org>
#
# Created:     Nov. 20, 2009
# Copyright:   Copyright 2009,2010 Crane Chu
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#-------------------------------------------------------------------------------
#!/usr/bin/env python

__author__ = 'Crane Chu'


# MODULES
import os
import sys
import net_io
import logging
import dir_cache
import data_cache
import gaedrive_config

#remote debug by pydev. insert following line anywhere as a break point...
#import pydevd; pydevd.settrace()


if __name__ == "__main__":
    from fuse import FUSE, Operations, LoggingMixIn
    cache_path = ''
else:
    # when testing, not use fuse!
    class LoggingMixIn: pass
    class Operations: pass
    cache_path = '/home/cranechu/GAE/GAEDrive/client/SDCache'
    
class GAEDriveClient(LoggingMixIn, Operations):
    """ client of GAEStorage. 
    implement the fuse file operation by talking with master and chunk servers
    """
    def __init__(self, mntpoint):
        self.net_io = net_io.NetIO()
        self.dir = dir_cache.DIRCache(self.net_io, cache_path)
        self.data = data_cache.DATACache(self.net_io, cache_path)
        self.openedfile = {}
        self.fd = 0
        self.mntpoint = os.path.abspath(mntpoint)

    def clear(self):
        self.dir.clear()
        self.data.clear()
        
######## redirect to dir cache module ############
    def chmod(self, path, mode):
        if self.dir.chmod(path.decode('utf-8'), mode):
            return 0

    def chown(self, path, uid, gid):
        self.dir.chown(path.decode('utf-8'), uid, gid)
            
    def create(self, path, mode):
        fd = 0
        if self.dir.create(path.decode('utf-8'), mode):
            fd = self.open(path, os.O_RDWR)

        return fd

    def open(self, path, flags):
        rand = self.dir.open(path.decode('utf-8'), flags)
        self.fd += 1
        self.openedfile[self.fd] = rand
        return self.fd

    def getattr(self, path, fh = None):
        return self.dir.getattr(path.decode('utf-8'))
        
    def rename(self, old, new):
        self.dir.rename(old.decode('utf-8'), new.decode('utf-8'))
        
    def mkdir(self, path, mode):
        self.dir.mkdir(path.decode('utf-8'), mode)

    def readdir(self, path, fh = None):
        return self.dir.readdir(path.decode('utf-8'))

    def utimens(self, path, times = None):
        self.dir.utimens(path.decode('utf-8'), times)

    def getxattr(self, path, name, position = 0):
        return self.dir.getxattr(path.decode('utf-8'), name)

    def setxattr(self, path, name, value, options = None, position = 0):
        self.dir.setxattr(path.decode('utf-8'), name, value)

    def listxattr(self, path):
        return self.dir.listxattr(path.decode('utf-8'))

    def removexattr(self, path, name):
        self.dir.removexattr(path.decode('utf-8'), name)

    def statfs(self, path):
        return self.dir.statfs(path.decode('utf-8'))

    def symlink(self, target, source):
        # convert to relaitive path to mntpoint
        source = os.path.abspath(source)
        source = source[len(self.mntpoint):]
        self.dir.symlink(target.decode('utf-8'), source.decode('utf-8'))

    def readlink(self, path):
        target = self.mntpoint + self.dir.readlink(path.decode('utf-8'))
        return target.encode('utf-8')

######## redirect to dir and data cache module ############
    def read(self, path, size, offset, fh):
        path = path.decode('utf-8')
        remain = size
        data = b''
        
        while remain > 0:
            chunk, read_size = self.dir.read(path, remain, offset)
            if not chunk:
                break
            read_size = min(read_size, remain)
            offset_in_chunk = offset % gaedrive_config.CHUNK_SIZE
            data += self.data.read(chunk,
                                   read_size,
                                   offset_in_chunk,
                                   self.openedfile[fh])
            remain -= read_size
            offset += read_size
            assert remain >= 0
        
        return data
    
    def write(self, path, data, offset, fh):
        path = path.decode('utf-8')
        remain = len(data)
        
        while remain > 0:
            chunk, size = self.dir.write(path,
                                         remain,
                                         offset,
                                         self.openedfile[fh])
            offset_in_chunk = offset % gaedrive_config.CHUNK_SIZE
            start_index = -remain
            end_index = -remain + size if size - remain else len(data)
            self.data.write(chunk,
                            offset_in_chunk,
                            data[start_index:end_index])
            remain -= size
            offset += size
            assert remain >= 0

        return len(data) - remain
    
    def flush(self, path, fh = None):
        chunks = self.dir.getchunks(path.decode('utf-8'), self.openedfile[fh])
        # flush data in NVM
        # TODO: upload data to netio in background thread
        logging.debug("fh=%d, rand=%d" % (fh, self.openedfile[fh]))
        for chunk in chunks:
            self.data.flush(chunk, self.openedfile[fh])
            
    def truncate(self, path, length, fh = None):
        rand = self.openedfile[fh] if fh else 0
        chunks = self.dir.truncate(path.decode('utf-8'), length, rand)
        if chunks:
            index = length // gaedrive_config.CHUNK_SIZE
            len_in_chunk = length % gaedrive_config.CHUNK_SIZE
            self.data.truncate(chunks[index], len_in_chunk)
            #delete other remaining chunks
            self.data.delete(chunks[index + 1:])

    def release(self, path, fh):
        # close the opened file
        if self.dir.release(path.decode('utf-8'), self.openedfile[fh]):
            del self.openedfile[fh]
        
    def unlink(self, path):
        chunks = self.dir.unlink(path.decode('utf-8'))
        if chunks:
            self.data.delete(chunks)

    def rmdir(self, path):
        chunks = self.dir.rmdir(path.decode('utf-8'))
        if chunks:
            self.data.delete(chunks)

    def destroy(self, path):
        return self.dir.destroy()
    
if __name__ == "__main__":
    if len(sys.argv) != 3:
        print "Usage:"
        print "python2.6 -O client_main.py mnt_point cache_dir"
    else:
        mntpoint = sys.argv[1]
        cache_path = sys.argv[2]
    
    logging.getLogger().setLevel(logging.DEBUG)
    fuse = FUSE(GAEDriveClient(mntpoint), mntpoint, foreground = True)
